/*
 *  mini_db.c
 *
 *  Used for storing matrices of data in a 'name'/'value' pair.
 *  These mini databases may be saved or loaded from a file.
 *  Data may be added, modified, retrieved, and removed.
 *
 * TODO: add functions for reading/writing HTTP style "headers: value" over
 *       the wire.
 *
 * Copyright (c) 2006 Karlan Thomas Mitchell<karlanmitchell@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *         This product includes software developed by
           Karlan Thomas Mitchell<karlanmitchell@gmail.com>
 * 4. Neither Karlan Thomas Mitchell nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY KARLAN THOMAS MITCHELL AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSBILITY OF SUCH DAMAGE.
 */


#include "mini_db.h"

/*The ultimate debug function*/
int mdb_printall(struct st_mdb * db_data){
      size_t items = mdb_count_items(db_data);
      size_t count = 1;
      while(count <= items){
         printf("\"%s\" \"%s\"\n", mdb_get_name_by_number(db_data, count), mdb_get_value_by_number(db_data, count));
          count++;
          }
      return 0;
     }

int mdb_save_tofd(struct st_mdb * db_data, int mdb_file){
	
	char * escape_buf = NULL;	
	size_t escape_len;
	size_t c = 0;
	size_t max = db_data->items;

	for(; c < max  ; c++){

		/*Skip Unused (Deleted) entries*/
		if(db_data->name[c] == NULL ){
			max++;
			continue;
			}

		escape_len = strlen(db_data->name[c]) + (uri_escape_count(db_data->name[c], strlen(db_data->name[c])) * 2) + 1;
		escape_buf = (char *)realloc(escape_buf, escape_len * sizeof(char));
		uri_escape(escape_buf, db_data->name[c], escape_len);

		write(mdb_file, escape_buf, strlen(escape_buf));
		write(mdb_file, " ", 1);
		write(mdb_file, "=", 1);
		write(mdb_file, " ", 1);


		escape_len = strlen(db_data->value[c]) + (uri_escape_count(db_data->value[c], strlen(db_data->value[c])) * 2) + 1;
		escape_buf = (char *)realloc(escape_buf, escape_len * sizeof(char));
		uri_escape(escape_buf, db_data->value[c], escape_len);

		write(mdb_file, escape_buf, strlen(escape_buf));
		write(mdb_file, "\n", 1);

		
	}
	write(mdb_file, "@", 1);


	free(escape_buf);

}

int mdb_save(struct st_mdb * db_data, char * filename){
	int mdb_file = open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
	mdb_save_tofd(db_data, mdb_file);
	close(mdb_file);
	return 1;

}

 int mdb_load_fromfd(struct st_mdb * db_data, int mdb_file){
	size_t buffer_size = 65;
	char * name        = (char *)calloc(buffer_size, sizeof(char));
	char * value       = (char *)calloc(buffer_size, sizeof(char));
	
	char data_buffer[2];
	data_buffer[1] = 0;

	int name_or_value = 1;

	char * cpntr;
	int rv;

	while(1){
 		rv = read(mdb_file, data_buffer, 1);

		if(rv <= 0)
			break;

		if(strlen(name) + 4 >= buffer_size ||
		   strlen(value) + 4 >= buffer_size){
		   buffer_size += 64;
		   name        = (char *)realloc(name, buffer_size * sizeof(char));
		   value       = (char *)realloc(value, buffer_size * sizeof(char));
		   }

		
		switch(*data_buffer){
			case '\n':
			case '=':
				name_or_value *= -1;
				
				if(name_or_value == 1){
					if(mdb_find_escaped(db_data, name) == 0)
						mdb_add_escaped_value(db_data, name, value);
					else
						printf("%s already present!\n");
					name[0] = 0;
					value[0] = 0;
					}
				continue;
			case ' ':
				continue;
			case '@':
				/*when abs of name_or_value isn't 1, finish/error assumed, 
			          and the main while loop is exited*/
				name_or_value = -99;
				
			}

		if(name_or_value == 1){
			strlcat(name, data_buffer, buffer_size);
		}
		else if(name_or_value == -1){
			strlcat(value, data_buffer, buffer_size);
			}
		else 
			break;


	}


	
	free(name);
	free(value);
}

int mdb_load(struct st_mdb * db_data, char * filename){
	if(is_file(filename) == 0)
		return 0;

	int mdb_file = open(filename, O_RDONLY, 0);
	mdb_load_fromfd(db_data, mdb_file);
	close(mdb_file);

	return 1;
}



int mdb_copy(struct st_mdb * new_db, struct st_mdb * old_db){
    size_t items = mdb_count_items(old_db);
    size_t count = 1;
    while(count <= items){
        mdb_set(new_db, mdb_get_name_by_number(old_db, count), mdb_get_value_by_number(old_db, count));
        count++;
        }
    return 0;
}

int mdb_init(struct st_mdb * db_data){
    /*Just in case this fail, a free won't do any harm*/
    db_data->allocated  = 1;
    db_data->items      = 0;
    db_data->name       = (char **)malloc(db_data->allocated * sizeof(char *));
    db_data->value      = (char **)malloc(db_data->allocated * sizeof(char *));
}

int mdb_free(struct st_mdb * db_data){
    size_t count = 0;
    while(count < db_data->items){
        free(db_data->name [count]);
        free(db_data->value[count]);
        count++;
        }
    free(db_data->name);
    free(db_data->value);
    db_data->name  = NULL;
    db_data->value = NULL;
    db_data->items = 0;
    db_data->allocated  = 0;
    return 0;
}


int mdb_reset(struct st_mdb * db_data){
    mdb_free(db_data);
    return mdb_init(db_data);
}


int mdb_add_escaped_value(struct st_mdb * db_data, char * name, char * value){
    size_t name_len   = strlen(name)  + 1;
    size_t value_len  = strlen(value) + 1;
    char * unescaped_name   = (char *)malloc(name_len  * sizeof(char));
    char * unescaped_value  = (char *)malloc(value_len * sizeof(char));
    uri_unescape(unescaped_name,  name, name_len);
    uri_unescape(unescaped_value, value, value_len);
    strcreplace(unescaped_name, '+', ' ');
    strcreplace(unescaped_value, '+', ' ');

    /*We don't want duplicates*/
    if(mdb_find(db_data, unescaped_name) == 0)
    	mdb_add_value(db_data, unescaped_name, unescaped_value);

    free(unescaped_name);
    free(unescaped_value);
    return 0;
}

int mdb_add_value(struct st_mdb * db_data, char * name, char * value){
	

    size_t name_len  = strlen(name) + 1;
    size_t value_len = strlen(value) + 1;
    size_t new_item  = db_data->allocated - 1;
    size_t count = 0;
 
    while(count < db_data->allocated){
	if(db_data->name[count] == NULL){
		new_item = count;
		break;
		}
    	count++;
    }
    

    db_data->name  [new_item] = (char *)malloc(name_len  * sizeof(char));
    db_data->value [new_item] = (char *)malloc(value_len * sizeof(char));
    strcpy(db_data->name  [new_item], name);
    strcpy(db_data->value [new_item], value);

    db_data->items++;
    if(db_data->items >= db_data->allocated){
    	db_data->allocated++;
    	db_data->name  = (char **)realloc(db_data->name,  (db_data->allocated) * sizeof(char *));
    	db_data->value = (char **)realloc(db_data->value, (db_data->allocated) * sizeof(char *));
	db_data->name[db_data->allocated - 1] = NULL;
	}
    return 0;
}



int mdb_set_value(struct st_mdb * db_data, size_t loc, char *value){
    db_data->value[loc] = (char *)realloc(db_data->value[loc], (strlen(value) + 1) * sizeof(char));
    strcpy(db_data->value[loc], value);
    return 0;
}


/*Remember, 0 is error, all other is one place down!*/
size_t mdb_find(struct st_mdb * db_data, char * name){
	size_t c = 0;
	size_t max = db_data->items;
	for( ; c < max ; c++){
		if(db_data->name[c] == NULL){
			max++;
			continue;
		}
		if(strcasecmp(name, db_data->name[c]) == 0)
			return c + 1;
		}
	return 0;
}

size_t mdb_find_escaped(struct st_mdb * db_data, char * name){
	
        size_t name_len   = strlen(name) + 1;
        char * unescaped_name   = (char *)malloc(name_len  * sizeof(char));

        uri_unescape(unescaped_name,  name, name_len);
        strcreplace(unescaped_name, '+', ' ');

	int search_item = mdb_find(db_data, unescaped_name);

	free(unescaped_name);


	return search_item;
}





int mdb_set(struct st_mdb * db_data, char * name, char * value){
	size_t loc = mdb_find(db_data, name);

	/*If entry doesn't exist*/
	if(loc == 0)
		return mdb_add_value(db_data, name, value);
	else
		return mdb_set_value(db_data, loc - 1, value);
}


int mdb_set_base32(struct st_mdb * db_data, char * name, char * value, size_t value_len){	
	size_t base32_len = value_len * 2 + 1;
 	char * base32_value = malloc(base32_len * sizeof(char));
	int returnval = 0;

	base32_encode(base32_value, base32_len, value, value_len);

	returnval = mdb_set(db_data, name, base32_value);
	free(base32_value);
	return returnval;
}



/*Works exactly the same as mdb_set when name doesn't exist */
int mdb_cat(struct st_mdb * db_data, char * name, char * value){
	size_t loc = mdb_find(db_data, name);
	size_t value_len = strlen(value);
	size_t previous_value_len;
	char * previous_value;
	char * new_value;
	int return_val = 0;

	/*Just add the value if there is nothing to concate to*/
	if(loc == 0)
		return mdb_add_value(db_data, name, value);

	loc--;
	previous_value     = db_data->value[loc];
	previous_value_len = strlen(previous_value);
	new_value = (char *)malloc(  (previous_value_len + value_len + 1) * sizeof(char)  );
	strcpy(new_value, previous_value);
	strcat(new_value, value);

	return_val = mdb_set_value(db_data, loc, new_value);

	free(new_value);
	return return_val;
}

int    mdb_set_double(struct st_mdb * db_data, char * name, double value){
	char * buffer = malloc(128);

	sprintf(buffer, "%f", value);

	mdb_set(db_data, name, buffer);
	free(buffer);
	return 0;
	}

int    mdb_set_int(struct st_mdb * db_data, char * name, int value){
	char * buffer = malloc(128);

	sprintf(buffer, "%d", value);

	mdb_set(db_data, name, buffer);
	free(buffer);
	return 0;
	}

int    mdb_set_off_t(struct st_mdb * db_data, char * name, off_t value){
	char * buffer = malloc(128);

	sprintf(buffer, "%" PRId64, value);

	mdb_set(db_data, name, buffer);
	free(buffer);
	return 0;
	}

int    mdb_set_address(struct st_mdb * db_data, char * name, void * value){
	char * buffer = malloc(128);

	sprintf(buffer, "%p", value);

	mdb_set(db_data, name, buffer);
	free(buffer);
	return 0;
	}


/*Exactly the same as mdb_get, except NULL is never returned*/
char * mdb_print(struct st_mdb * db_data, char * name){
	char * print_what = mdb_get(db_data, name);
	if(print_what == NULL)
		return "\0";
	return print_what;

}

char * mdb_get(struct st_mdb * db_data, char * name){
	size_t loc = mdb_find(db_data, name);
	if(loc == 0)
		return NULL;
	else
		return db_data->value[loc - 1];
}

void *  mdb_get_address(struct st_mdb * db_data, char * name){
	char * buffer = malloc(128);

	sprintf(buffer, "%d", strtoll(mdb_print(db_data, name), NULL, 16));

	void * ref_addr = ((void *)atol(buffer));

	free(buffer);


	return ( ref_addr );
	}

double mdb_get_double(struct st_mdb * db_data, char * name){
	return atof(  mdb_print(db_data, name)  );
	}

int mdb_get_int(struct st_mdb * db_data, char * name){
	return atoi(  mdb_print(db_data, name)  );
	}
off_t mdb_get_off_t(struct st_mdb * db_data, char * name){
	return atooff_t(  mdb_print(db_data, name)  );
	}


char * mdb_get_name_by_number(struct st_mdb * db_data, int number){
	size_t item_num = 0;
	size_t c = 0;
	size_t max = db_data->items;
	for( ; c < max ; c++){
		if(db_data->name[c] == NULL){
			/*Empty Item*/
			max++;
			continue;
		}
		else 
			item_num++;
		if(item_num == number)
			return db_data->name[c];
	}
	return NULL;
}
char * mdb_get_value_by_number(struct st_mdb * db_data, int number){
	size_t item_num = 0;
	size_t c = 0;
	size_t max = db_data->items;
	for( ; c < max ; c++){
		if(db_data->name[c] == NULL){
			max++;
			continue;
		}
		else 
			item_num++;

		if(item_num == number)
			return db_data->value[c];
	}
	return NULL;
}

size_t mdb_count_items(struct st_mdb * db_data){
	size_t items = 0;
	size_t c = 0;
	size_t max = db_data->items;
	for( ; c < max ; c++){
		if(db_data->name[c] == NULL){
			max++;
			continue;
		}
		else 
			items++;
	}
	return items;
}

int mdb_remove(struct st_mdb * db_data, char * name){
	size_t loc = mdb_find(db_data, name);
	if(loc == 0)
		return 0;
	loc -= 1;
	free(db_data->value[loc]);
	free(db_data->name[loc]);

	db_data->name [loc] = NULL;
	db_data->value[loc] = NULL;
        db_data->items--;
	return 1;
}
